package com.demo.search.engine.annotation;

import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.lucene.document.Document;
import org.apache.lucene.document.Field;
import org.apache.lucene.document.Fieldable;
import org.apache.lucene.document.Field.Index;
import org.apache.lucene.document.Field.Store;
import org.apache.lucene.document.Field.TermVector;
import org.apache.lucene.index.IndexWriter;
import org.apache.lucene.index.Term;

import com.demo.common.HtmlUtils;
import com.demo.common.StringUtils;
import com.demo.search.annotation.Searchable;
import com.demo.search.annotation.SearchableId;
import com.demo.search.annotation.SearchableProperty;
import com.demo.search.engine.HighlighterHandler;
import com.demo.search.engine.SearchEngine;
import com.demo.search.exception.IndexException;
import com.demo.search.exception.TargetNotFoundException;
import com.demo.search.model.FieldModel;
import com.demo.search.util.LuceneUtils;

/**
 * 注解形式模型-索引引擎
 * <ul><li>
 * 将带有索引注解的模型转化为Lucene索引字段Field</li><li>
 * 将查询结果对象Document转化为带有索引注解的模型列表</li></ul>
 * 
 * @author ryuu.kk
 * @version 1.5
 *
 */
public class AnnotationSearchEngine implements SearchEngine {
	
	/**
	 * 字段缓存Map
	 * 对象模型类可索引字段进行缓存
	 */
	private Map<Class<?>, List<FieldModel>> fieldCache = new ConcurrentHashMap<Class<?>, List<FieldModel>>();
	
	/**
	 * 索引写入对象
	 */
	private IndexWriter indexWriter;
	
	/**
	 * 构造方法
	 * @param indexWriter
	 */
	public AnnotationSearchEngine(IndexWriter indexWriter) {
		this.indexWriter = indexWriter;
	}
	
	@Override
	public boolean support(Object target) {
		return true;
	}
	
	@Override
	public List<Fieldable> objectToFields(Object obj) {
				
		List<Fieldable> fieldList = new ArrayList<Fieldable>();
		// 对象类分解
		fieldList.add(searchableBuilder(obj));
		try {
			// 对象主键分解
			fieldList.add(searchableIdBuilder(obj, obj.getClass()));
			// 对象属性值分解
			fieldList.addAll(searchablePropertyBuilder(obj, obj.getClass()));
			
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		
		return fieldList;
	}
	
	@Override
	public <T> List<T> documentToTarget(List<Document> docList, Class<T> clazz)
			throws IllegalArgumentException, IllegalAccessException,
			InstantiationException {
		return documentToTarget(docList, clazz, null, Integer.MAX_VALUE);
	}
	
	@Override
	public <T> List<T> documentToTarget(List<Document> docList, Class<T> clazz, int maxLength) 
			throws IllegalArgumentException, IllegalAccessException, InstantiationException {
		
		return documentToTarget(docList, clazz, null, maxLength);
	}
	
	@Override
	public <T> List<T> documentToTarget(List<Document> docList, Class<T> clazz, HighlighterHandler highlighter)
			throws IllegalArgumentException, IllegalAccessException, InstantiationException {
		
		return documentToTarget(docList, clazz, highlighter, Integer.MAX_VALUE);
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public <T> List<T> documentToTarget(List<Document> docList, Class<T> clazz,
			HighlighterHandler highlighter, int maxLength)
			throws IllegalArgumentException, IllegalAccessException,
			InstantiationException {
		List<T> results = null;
		// 获得对象类的字段模型
		List<FieldModel> fieldModelList = getFieldModelList(clazz);
		
		if (docList != null) {
			results = new ArrayList<T>();
			for (int docId = 0; docId < docList.size(); docId ++) {
				Document doc = docList.get(docId);
                // 生成对象实例
				T t = clazz.newInstance();
				for (FieldModel model : fieldModelList) {
					Class<?> type = model.getField().getType();
					// 设置字段访问权
					model.getField().setAccessible(true);
					Type gt = model.getField().getGenericType();
					String name = model.getName();
					String value = doc.get(name);;

					// 泛型类型判断
					if (gt instanceof ParameterizedType) {   
			            // 执行强制类型转换   
			            ParameterizedType parameterizedType = (ParameterizedType)gt;   
			            // 获取基本类型信息 
			            Type basicType = parameterizedType.getRawType();   
			            
			            Object obj = null;
			            if (basicType.equals(java.util.List.class)) {
			            	obj = new ArrayList();
			            	// 获取泛型类型的泛型参数   
				            Type[] types = parameterizedType.getActualTypeArguments();
				            // 泛型参数
				            Type paramType = null;
				            if (types != null && types.length > 0) {
				            	paramType = types[0];
				            }
				            // 获得泛型对象类的字段模型
				    		List<FieldModel> contentFieldModelList = getFieldModelList((Class) paramType);
				    		Object paramTarget = null;
				    		for (FieldModel m : contentFieldModelList) {
				    			// 判断符合类型的泛型对象的注解类型与检索字段一致
				    			if (m.getName().equals(model.getName())) {
				    				// 生成泛型对象
				    				paramTarget = ((Class) paramType).newInstance();
				    				m.getField().setAccessible(true);
									
									if (StringUtils.isNotEmpty(value) 
											&& m.getField().getType().equals(java.lang.String.class)) {
										// 如果超过指定限额截取字符串
										if (value != null && value.length() > maxLength) {
											value = StringUtils.subString(value, maxLength, "...", "Unicode");
										}										
										// 高亮检索结果
										if (highlighter != null && highlighter.support(model)) {
											//value = highlighter.render(doc, docId, model);
											value = highlighter.render(value, model);
										}
									}
						            m.getField().set(paramTarget, value);
				    			}
				    		}
				    		((List)obj).add(paramTarget);
			            } else if (obj instanceof Map) {
			            	 // 获取泛型类型的泛型参数   
				            Type[] types = parameterizedType.getActualTypeArguments();
				            Type keyType = null;
				            Type valueType = null;
				            if (types != null && types.length > 1) {
				            	keyType = types[0];
				            	valueType = types[1];
				            }
				            System.out.print(keyType + ":" + valueType);
			            } else {
			            	// 其他类型,无他处理
			            }
			            model.getField().set(t, obj);
			        } else {   
						if (StringUtils.isNotEmpty(value) 
								&& type.equals(java.lang.String.class)) {
							// 如果超过指定限额截取字符串
							if (value != null && value.length() > maxLength) {
								value = StringUtils.subString(value, maxLength, "...", "Unicode");
							}
							// 高亮检索结果
							if (highlighter != null && highlighter.support(model)) {
								//value = highlighter.render(doc, docId, model);
								value = highlighter.render(value, model);
							}
						}
			            //简单类型,做一般处理
			            model.getField().set(t, LuceneUtils.typeTransformation(value, type, model.getFormat()));
			        }   
					//t.getClass().getDeclaredField(fieldName)
				}
				results.add(t);
			}
		}
		return results;
	}
	
	@Override
	public <T> void index(List<T> objectList, boolean autoCommit) throws IndexException {
		
		try {
			if (objectList != null) {
				for (Object obj : objectList) {
					List<Fieldable> fieldList = objectToFields(obj);
					Document doc = new Document();
					if (fieldList != null) {
						for (Fieldable field : fieldList) {
							if (field != null) {
								doc.add(field);
							}
						}
						indexWriter.addDocument(doc);
					}
				}
				if (autoCommit) {
					indexWriter.commit();
				}
			}
		} catch (Exception e) {
			try {
				indexWriter.rollback();
			} catch (IOException e1) {
			}
			throw new IndexException(e.getMessage(), e.getCause());
		} 
	}
	
	@Override
	public int createIndex(BufferedReader br, String split, String[] name, Store[] store, Index[] index) throws IOException {
		String line = br.readLine();
		int cnt = 0;
		while (line != null) {
			line = line.trim();
			if (line.length() > 0) {
				// parse row and create a document
				StringTokenizer st = new StringTokenizer(line, split);
				Document doc = new Document();
				int idx = 0;
				while(st.hasMoreElements()) {
					doc.add(new Field(name[idx], st.nextToken(), store[idx], index[idx]));
					idx ++;
				}
				indexWriter.addDocument(doc);
				cnt ++;
			}
			line = br.readLine();
		}
		indexWriter.commit();
		return cnt;
	}
	
	@Override
	public void createIndex(String name, String value, Store store, Index index, boolean autoCommit) throws IOException {
		Document doc = new Document();
		doc.add(new Field(name, value, store,index));
		indexWriter.addDocument(doc);
		if (autoCommit) {
			indexWriter.commit();
		}
	}
	
	@Override
	public void createIndex(List<Fieldable> fieldList) throws IOException {
		Document doc = new Document();
		for (Fieldable field : fieldList) {
			doc.add(field);
		}
		indexWriter.addDocument(doc);
	}
	
	@Override
	public void createIndex(Object target) throws IOException {
		Document doc = new Document();
		// 取得索引信息
		List<Fieldable> fieldList = objectToFields(target);
		try {
			for (Fieldable field : fieldList) {
				if (field != null) {
					doc.add(field);
				}
			}
			// 创建索引
			indexWriter.addDocument(doc);
		} catch (Exception e) {
			try {
				indexWriter.rollback();
			} catch (IOException e1) {}
			throw new IndexException(e.getMessage(), e.getCause());
		}
		try {
			indexWriter.commit();
		} catch (Exception e) {
			// some-things.
		}
	}
	
	@Override
	public void updateIndex(Term term, List<Fieldable> fieldList) throws IndexException {
		Document doc = new Document();
		for (Fieldable field : fieldList) {
			doc.add(field);
		}
		try {
			indexWriter.updateDocument(term, doc);
		} catch (Exception e) {
			throw new IndexException(e.getMessage(), e.getCause());
		} 
	}
	
	@Override
	public void updateIndex(Long id, Object target) throws IndexException {
		// 取得索引信息
		List<Fieldable> fieldList = objectToFields(target);
		Fieldable fieldPk;
		try {
			fieldPk = searchableIdBuilder(target, target.getClass());
			Term term = new Term(fieldPk.name(), String.valueOf(id));
			Document doc = new Document();
			for (Fieldable field : fieldList) {
				doc.add(field);
			}
			// 更新索引
			indexWriter.updateDocument(term, doc);
		} catch (Exception e) {
			throw new IndexException(e.getMessage(), e.getCause());
		} 
	}
	
	@Override
	public void updateIndex(Object target) throws IndexException {
		// 取得索引信息
		List<Fieldable> fieldList = objectToFields(target);
		Fieldable fieldPk;
		try {
			fieldPk = searchableIdBuilder(target, target.getClass());
			Term term = new Term(fieldPk.name(), fieldPk.stringValue());
			Document doc = new Document();
			for (Fieldable field : fieldList) {
				doc.add(field);
			}
			// 更新索引
			indexWriter.updateDocument(term, doc);
		} catch (Exception e) {
			try {
				indexWriter.rollback();
			} catch (IOException e1) {}
			throw new IndexException(e.getMessage(), e.getCause());
		}
		try {
			indexWriter.commit();
		} catch (Exception e) {
			// some-things.
		}
	}
	
	
	@Override
	public void updateIndexList(List<Object> objList) throws IndexException {
		try {
			for (Object target : objList) {
				updateIndex(target);
			}
		} catch (Exception e) {
			try {
				indexWriter.rollback();
			} catch (IOException e1) {
				//do-somethings
			}
			throw new IndexException(e.getMessage(), e.getCause());
		}
		try {
			indexWriter.commit();
		} catch (Exception e) {
			// some-things.
		}
	}
	
	@Override
	public void deleteIndex(String key, String value) throws IndexException {
		Term term = new Term(key, value);
		try {
			indexWriter.deleteDocuments(term);
			indexWriter.commit();
		} catch (Exception e) {
			try {
				indexWriter.rollback();
			} catch (IOException e1) {
			}
			throw new IndexException(e.getMessage(), e.getCause());
		}
	}
	
	@Override
	public void deleteIndex(Object target) throws IndexException {
		
		deleteIndex(target, true);
	}
	
	@Override
	public void deleteIndexList(List<Object> objList) {
		
		try {
			for (Object target : objList) {
				deleteIndex(target, false);
			}
			indexWriter.commit();
		} catch (Exception e) {
			try {
				indexWriter.rollback();
			} catch (IOException e1) {
			}
		}
	}
	
	@Override
	public void deleteAll() {
		try {
			indexWriter.deleteAll();
		} catch (IOException e) {
			throw new IndexException(e.getMessage(), e.getCause());
		}
	}
	
	// ~~ protected settings -------------------------------------------------------------------------------
	
	protected void deleteIndex(Object target, boolean autoCommit) throws IndexException {
		
		Fieldable fieldPk = null;
		Term term = null;
		
		try {
			fieldPk = searchableIdBuilder(target, target.getClass());
			if (fieldPk == null) {
				throw new TargetNotFoundException("target object not found annotation " + SearchableId.class);
			}
			term = new Term(fieldPk.name(), fieldPk.stringValue());
			indexWriter.deleteDocuments(term);
			if (autoCommit)indexWriter.commit();
		} catch (Exception e) {
			try {
				indexWriter.rollback();
			} catch (IOException e1) {
			}
			throw new IndexException(e.getMessage(), e.getCause());
		}
	}
	
	@Override
	public IndexWriter ownerIndexWriter() {
		return indexWriter;
	}

	/**
	 * 获得字段模型列表
	 * @param clazz 目标对象
	 * @return 对象模型列表
	 */
	@SuppressWarnings("rawtypes")
	protected List<FieldModel> getFieldModelList(Class clazz) {
		
		List<FieldModel> fieldModelList = null;
		if (clazz == null ) {
			return null;
		}
		fieldModelList = fieldCache.get(clazz);
		
		if (fieldModelList == null || fieldModelList.size() == 0) {
			java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
			fieldModelList = new ArrayList<FieldModel>();
			for (java.lang.reflect.Field f : fields){
				String name = "" ; //fieldCache.get(f);
				SearchableId searchableId = f.getAnnotation(SearchableId.class);
				SearchableProperty searchableProperty = f.getAnnotation(SearchableProperty.class);
				if (searchableId != null) {
					name = searchableId.name();
					if (StringUtils.isEmpty(name)) {
						name = f.getName();
					}
					FieldModel model = new FieldModel();
					model.setField(f);
					model.setName(name);
					fieldModelList.add(model);
				}
				if (searchableProperty != null) {
					name = searchableProperty.name();
					if (StringUtils.isEmpty(name)) {
						name = f.getName();
					}
					FieldModel model = new FieldModel();
					model.setField(f);
					model.setName(name);
					// 允许高亮
					model.setHighlighter(searchableProperty.allowHighlighter());
					fieldModelList.add(model);
				}
			}
			// 超找超类的指定注解
			List<FieldModel> tmpList = getFieldModelList(clazz.getSuperclass());
			if (tmpList != null) {
				fieldModelList.addAll(tmpList);
				fieldCache.put(clazz, fieldModelList);
			}
		}
		return fieldModelList;
	}
	
	/**
	 * 对象类分解器
	 * @param target 目标对象
	 * @param searchable 对象注解
	 * @param fieldList Field对象
	 */
	protected Fieldable searchableBuilder(Object target) {
		Searchable searchable = target.getClass().getAnnotation(Searchable.class);
		if (searchable == null) {
			throw new IndexException("target class must be settings annotation : " + Searchable.class);
		}
		String clazz = searchable.alias().equals("") ? target.getClass().getSimpleName() : searchable.alias();
		float boost = searchable.boost();
		//String[] extend = searchable.extend();
		//boolean isRoot = searchable.root();
		Field field = new Field(SearchEngine.PARSER_TABLE, clazz, Field.Store.YES, Field.Index.NOT_ANALYZED_NO_NORMS);
		field.setBoost(boost);
		
		return field;
	}
	
	/**
	 * 对象主键分析器(单主键)
	 * @param target 目标对象
	 * @param fieldList 对象索引的Field列表
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	protected Fieldable searchableIdBuilder(Object target, Class<?> clazz) throws IllegalArgumentException, IllegalAccessException {
		
		//List<Fieldable> fieldList = new ArrayList<Fieldable>();
		java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
//		if (target instanceof Class<?>) {
//			fields = ((Class<?>) target).getDeclaredFields();
//		} else {
//			fields = target.getClass().getDeclaredFields();
//		}
		
		Field field = null;
		
		for (java.lang.reflect.Field f : fields) {
			SearchableId searchableId = f.getAnnotation(SearchableId.class);
			if (searchableId != null) {
				String name = searchableId.name();
				// 默认使用字段名作为索引key
				if (StringUtils.isEmpty(name)) {
					name = f.getName();
				}

				Object value;
				f.setAccessible(true);
				value = f.get(target);

				// String converter = searchableId.converter();
				// String format = searchableId.format();

				float boost = searchableId.boost();
				Store store = searchableId.store();
				Index index = searchableId.index();
				// TermVector termVector = searchableId.termVector();
				// field = new NumericField(name, store, true);
				// set value
				// LuceneUtils.setValue(field, value, f.getType(), "");
				value = LuceneUtils.typeTrans(value, f.getType());
				field = new Field(name, String.valueOf(value), store, index);
				field.setBoost(boost);
				//fieldList.add(field);
				break;
			}
		}
		
		// 如果多主键联合,这里应该考虑主键的联合查询....
		
		if (field == null) {
			return searchableIdBuilder(target, clazz.getSuperclass());
		}
		return field ;
	}
	
	/**
	 * 对象属性分析器
	 * 如果对象存在集合类型,如List属性并且该属性配置注解{@link SearchableProperty},
	 * 则递归集合类型,找到类型中的内容进行合并
	 * @param target 目标对象
	 * @param fieldList 对象索引的Field列表
	 * @throws IllegalArgumentException
	 * @throws IllegalAccessException
	 */
	@SuppressWarnings("unchecked")
	protected List<Fieldable> searchablePropertyBuilder(Object target, Class<?> clazz) throws IllegalArgumentException, IllegalAccessException {
		
		List<Fieldable> fieldList = new ArrayList<Fieldable>();
		java.lang.reflect.Field[] fields = clazz.getDeclaredFields();
		for (java.lang.reflect.Field f : fields){
			
			SearchableProperty searchableProperty = f.getAnnotation(SearchableProperty.class);
			if (searchableProperty == null) {
				continue;
			}
			
			String name = searchableProperty.name();
			if (StringUtils.isEmpty(name)) {
				name = f.getName();
			}
			f.setAccessible(true);
			Object value = "";
			// 如果属性为集合类型
			if (List.class.isAssignableFrom(f.getType())) {
				/* **************************************************************************************************
				 * 例如一个模型对象存在一个List属性对象,并且需要对此进行索引,
				 * 在集合类型中的某个或某几个字段配置SearchableProperty属性后,
				 * 定义的对象将不再考虑属性List中的每个对象是否定义属性Searchable,
				 * 而是判断该集合对象中的每个属性是否配置SearchableProperty即可。
				 * 并且SearchableProperty只对字段起到标记作用,配置的名称将由定义它的目标对象的SearchableProperty属性配置替代。
				 * ************************************************************************************************* */
				List<? extends Object> resultList = (List<? extends Object>) f.get(target);
				if (resultList != null) {
					List<Fieldable> listFieldList = new ArrayList<Fieldable>();
					for (Object obj : resultList) {
						listFieldList.addAll(searchablePropertyBuilder(obj, obj.getClass()));
					}
					if (listFieldList.size() > 0) {
						String strContent = "";
						for (Fieldable field : listFieldList) {
							if (field != null) {
								strContent += field.stringValue();
							}
						}
						value = strContent;
					}
				}
				
			} else if (Map.class.isAssignableFrom(f.getType())) {
				// ....
			} else {
				value = f.get(target);
			}
			
			// 空值时使用注解的默认值
			if (value == null || StringUtils.isEmpty(value.toString())) {
				value = searchableProperty.nullValue();
			}
			
			// format 格式化结果
			String format = searchableProperty.format();
			
			// 对象-字符串转换
			value = LuceneUtils.objectTransformation(value, format);

			//String converter = searchableProperty.converter();
			String propertyConverter = searchableProperty.propertyConverter();
			
			if ("html".equalsIgnoreCase(propertyConverter)) {
				// 去除html操作
				value = HtmlUtils.htmlRemove(String.valueOf(value));
			}
			
			/* **********************************************************
			 * 获得索引关键变量
			 * boost、Index、Store、TermVector
			 ************************************************************ */
			//value = String.format(format, value);
			float boost = searchableProperty.boost();
			Index index = searchableProperty.index();
			Store store = searchableProperty.store();
			TermVector termVector = searchableProperty.termVector();

			Field field = new Field(name, (String)value, store, index, termVector);
			field.setBoost(boost);
			fieldList.add(field);
		}
		if (clazz.getSuperclass() != null) {
			fieldList.addAll(searchablePropertyBuilder(target, clazz.getSuperclass()));
		}
		
		return fieldList;
	}
	//	private class AnnotationFieldCallback implements FieldCallback {
//
//		@Override
//		public void doWith(java.lang.reflect.Field field)
//				throws IllegalArgumentException, IllegalAccessException {
//			// TODO Auto-generated method stub
//			
//		}
//	}
//	
//	private class AnnotationFieldFilter implements FieldFilter {
//
//		@Override
//		public boolean matches(java.lang.reflect.Field field) {
//			field.getAnnotation(SearchableProperty.class);
//			return false;
//		}
//	}
//	
//	private <T> T getAnnotation(Class clazz, Annotation ann) {
//		return (T) clazz.getAnnotation(ann.getClass());
//	}


}
